using System;
using System.Linq;
using Arcane.Source.Data;
using Arcane.Source.Combat;
using Godot;
using Arcane.Source.Utils;
using Arcane.Source.Client.Extensions;

namespace Arcane.Source.Client
{
    [Tool]
    public partial class RuneMatcher : Control
    {
        [Export] int col = 7, row = 10;
        [Export] Color boardDark = new(.6f, .6f, .6f), boardLight = new(.2f, .2f, .2f);
        [Export] Texture2D runeBackgrounds;

        public Func<Vector2I, Vector2I, bool> DoSwap;

        public float CellSize { get; private set; }
        public Rect2 BoardRect { get; private set; }
        public Texture2D[] RuneBackgrounds { get; private set; }
        public Texture2D[,] RuneTextures { get; private set; }

        public float TimeScale { get; set; } = 1;

        public RuneCell Holding { get; private set; } = null;

        private bool setup = false;

        private Texture2D boardTexture;

        private readonly RuneCell[,] runes = new RuneCell[Rule.COL, Rule.ROW];

        private Vector2I? prevCell = null;
        private RuneCell _hintedA = null, _hintedB = null;

        public int RuneDropFrame { get; private set; }

        public override void _Ready()
        {
            OnResize();
            Resized += OnResize;

            QueueRedraw();

            if (Engine.IsEditorHint()) SetPhysicsProcess(false);
        }

        public void Setup(RuneMatch match, StringName[] runeIds)
        {
            RuneBackgrounds = runeBackgrounds.SplitSquares();
            CacheRuneTextures(runeIds);

            match.OnRuneUpdate += SyncRune;
            DoSwap = (p1, p2) =>
            {
                return match.TrySwap(p1.X, p1.Y, p2.X, p2.Y);
            };
            match.OnTimeScaleChange += (t) => TimeScale = t;
            RuneDropFrame = match.RuneDropFrame;
            match.ShowHint += MakeHint;
            SetupRunes();

            setup = true;
        }

        public void OnResize()
        {
            var f = GetRect();

            var fw = f.Size.X;
            var fh = f.Size.Y;

            var a = (double)col / row;
            var fa = (double)fw / fh;

            if (fa > a)
            {
                var size = (double)fh / row;

                var bw = size * col;
                var bh = (float)size * row;
                var bx = (float)(0.5 * (fw - bw));

                CellSize = (float)size;
                BoardRect = new(bx, 0, (float)bw, bh);
            }
            else
            {
                var size = (double)fw / col;

                var bw = (float)size * col;
                var bh = size * row;
                var by = (float)(0.5 * (fh - bh));

                CellSize = (float)size;
                BoardRect = new(0, by, bw, (float)bh);
            }

            if (!setup) return;


            var runeSize = CalRuneSize();

            for (int r = 0; r < Rule.ROW; r++)
            {
                for (int c = 0; c < Rule.COL; c++)
                {
                    runes[c, r].Position = GetCellPivot(c, r);
                    runes[c, r].Resize(runeSize);
                }
            }
        }

        #region Board Rendering
        public override void _Draw()
        {
            if (boardTexture == null) GenBoardTexture();
            DrawTextureRect(boardTexture, BoardRect, false);
            // if (hint is Vector4I h)
            // {
            //     var b = new Vector2(h.X, h.Y) * CellSize;
            //     var s = new Vector2(h.Z - h.X, h.W - h.Y) * CellSize;
            //     DrawRect(new(b, s), Colors.Red);
            // }
        }

        public void MakeHint(int fromX, int fromY, int toX, int toY)
        {
            _hintedA = runes[fromX, fromY];
            _hintedA.HintTo(GetCellPivot(toX, toY));

            _hintedB = runes[toX, toY];
            _hintedB.HintTo(GetCellPivot(fromX, fromY));
            // hint = new(fromX, fromY, toX, toY);
            // GD.Print(hint);
            // QueueRedraw();
        }

        public void Unhint()
        {
            if (_hintedA == null) return;

            _hintedA.InstantBack();
            _hintedA = null;

            if (_hintedB == null) return;

            _hintedB.InstantBack();
            _hintedB = null;
        }

        private void GenBoardTexture()
        {
            var img = Image.CreateEmpty(col, row, false, Image.Format.Rgba8);
            for (int x = 0; x < col; x++)
            {
                for (int y = 0; y < row; y++)
                {
                    img.SetPixel(x, y, (x + y) % 2 == 0 ? boardLight : boardDark);
                }
            }

            boardTexture = ImageTexture.CreateFromImage(img);
        }

        public Vector2 GetCellPivot(Vector2I cell) => BoardRect.Position + CellSize * (cell + new Vector2(0.5f, 0.5f));
        public Vector2 GetCellPivot(int col, int row) => GetCellPivot(new(col, row));

        public Vector2 GetCellGlobalPivot(Vector2I cell) => GetCellPivot(cell) + GlobalPosition;
        public Vector2 GetCellGlobalPivot(int col, int row) => GetCellGlobalPivot(new(col, row));

        public Vector2I GetCell(Vector2 position) => ((position - BoardRect.Position) / CellSize).FloorToInt();
        public Vector2I GetCellFromGlobal(Vector2 globalPosition) => GetCell(globalPosition - GlobalPosition);

        public Vector2 GetCellSpawnPosition(int col) => GetCellPivot(col, -1);
        public int CalRuneSize() => Mathf.Clamp((int)(0.875f * CellSize / 16), 1, int.MaxValue) * 16;
        #endregion

        #region Rune Management
        public override void _GuiInput(InputEvent e)
        {
            if (e is InputEventMouseButton mbe)
            {
                if (mbe.ButtonIndex == MouseButton.Left)
                {
                    if (mbe.Pressed)
                    {
                        var cell = GetCellFromGlobal(mbe.GlobalPosition);
                        if (runes[cell.X, cell.Y].Phase == RunePhase.Idle)
                        {
                            Holding = runes[cell.X, cell.Y];
                            prevCell = cell;
                            Holding.Pick();
                        }
                    }
                    else if (Holding != null)
                    {
                        Holding.Unpick();
                        if (prevCell is Vector2I prev && prev != Holding.Cell)
                        {
                            if (DoSwap?.Invoke(Holding.Cell, prev) == false)
                            {
                                runes[prev.X, prev.Y].Back();
                            }
                        }
                        Holding = null;
                    }
                }
            }

            if (Holding != null && e is InputEventMouseMotion mme)
            {
                var mCell = GetCellFromGlobal(mme.GlobalPosition);
                var aCell = Rule.AVALIABLE_CELLS.Select(c => c + Holding.Cell).Where(CellValid).OrderBy(mCell.DistanceSquaredTo).First();

                if (aCell != prevCell)
                {
                    Holding.MoveTo(GetCellPivot(aCell));

                    if (aCell != Holding.Cell)
                    {
                        runes[aCell.X, aCell.Y].MoveTo(GetCellPivot(Holding.Cell));
                    }

                    if (prevCell is Vector2I prev && prev != Holding.Cell)
                    {
                        runes[prev.X, prev.Y].Back();
                    }

                    prevCell = aCell;
                }
            }
        }

        public void Unhold()
        {
            Holding.InstantBack();
            Holding = null;
        }

        private void CacheRuneTextures(StringName[] runeIds)
        {
            if (RuneTextures?.GetLength(0) != runeIds.Length) RuneTextures = new Texture2D[runeIds.Length, 3];
            for (var i = 0; i < runeIds.Length; i++)
            {
                var data = GameDB.Rune.Get(runeIds[i]);

                var bg = GameDB.Element.Get(data.element).Shape.GetImage();
                for (var l = 0; l < 3; l++)
                {
                    var runeTex = Image.CreateEmpty(18, 18, false, Image.Format.Rgba8);

                    runeTex.Stack(bg, 1, 1);
                    runeTex.Stack(data.GetTexture(l).GetImage(), 1, 1);

                    runeTex.GenOutline(Colors.Black);

                    RuneTextures[i, l] = ImageTexture.CreateFromImage(runeTex);
                }
            }
        }

        public void SetupRunes()
        {
            var runeSize = CalRuneSize();
            for (int r = 0; r < Rule.ROW; r++)
            {
                for (int c = 0; c < Rule.COL; c++)
                {
                    var rune = new RuneCell(this);
                    AddChild(rune);
                    rune.Setup(c, r);
                    rune.Resize(runeSize);
                    runes[c, r] = rune;
                }
            }
        }
        public void SyncRune(RuneData data, int col, int row)
        {
            Unhint();
            if (Holding?.PositionAt(col, row) == true && Holding?.phase != data?.phase)
            {
                Unhold();
            }
            runes[col, row].Sync(data);
        }
        public bool CellValid(Vector2I cell) => Rule.CellValid(cell) && runes[cell.X, cell.Y].Moveable();
        #endregion


    }
}